﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Web.Profile;
using AspAuthentication.Core;
using AspAuthentication.Interfaces;
using AspAuthentication.Persistence;

namespace AspAuthentication.Providers
{
    /// <summary>
    /// Defines the class PortalProfileProvider
    /// </summary>
    public class PortalProfileProvider : ProfileProvider, IPortalProfileProvider
    {
        /// <summary>
        /// Gets or sets the name of the connection string.
        /// </summary>
        /// <value>The name of the connection string.</value>
        public string ConnectionStringName { get; set; }
        /// <summary>
        /// Gets or sets the name of the currently running application.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.String"/> that contains the application's shortened name, which does not contain a full path or extension, for example, SimpleAppSettings.</returns>
        public override string ApplicationName { get; set; }
        private IUnitOfWork _unitOfWork;
        /// <summary>
        /// Gets or sets a value indicating whether [create database objects].
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [create database objects]; otherwise, <c>false</c>.
        /// </value>
        public bool CreateDatabaseObjects { get; set; }
        /// <summary>
        /// Gets the unit of work.
        /// </summary>
        /// <value>The unit of work.</value>
        public IUnitOfWork UnitOfWork
        {
            get { return _unitOfWork ?? (_unitOfWork = new UnitOfWork(ConnectionStringName,CreateDatabaseObjects)); }
        }

        /// <summary>
        /// Gets the property values.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="settingsProperties">The settings properties.</param>
        /// <returns></returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection settingsProperties)
        {
            var userName = context["UserName"].ToString();
            var isAuthenticated = Convert.ToBoolean(context["IsAuthenticated"]);

            var profile = UnitOfWork.RetrieveDomainObject<Profile>(p => p.Username == userName && p.Authenticated == isAuthenticated && p.ApplicationName == ApplicationName);
            return profile.PropertyValues;
        }

        /// <summary>
        /// Sets the property values.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="settingsPropertyValues">The settings property values.</param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection settingsPropertyValues)
        {
            var profile = new Profile().Populate(context).PopulatePropertyData(settingsPropertyValues);
            var currentDate = DateTime.Now;
            profile.LastActivityDate = currentDate;
            profile.LastUpdatedDate = currentDate;
            profile.ApplicationName = ApplicationName;
            UnitOfWork.Create(profile);
        }

        /// <summary>
        /// When overridden in a derived class, deletes all user-profile data for profiles in which the last activity date occurred before the specified date.
        /// </summary>
        /// <param name="authenticationOption">One of the <see cref="T:System.Web.Profile.ProfileAuthenticationOption"/> values, specifying whether anonymous, authenticated, or both types of profiles are deleted.</param>
        /// <param name="userInactiveSinceDate">A <see cref="T:System.DateTime"/> that identifies which user profiles are considered inactive. If the <see cref="P:System.Web.Profile.ProfileInfo.LastActivityDate"/>  value of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
        /// <returns>
        /// The number of profiles deleted from the data source.
        /// </returns>
        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            var deleteSql = authenticationOption.Equals(ProfileAuthenticationOption.All) ?
                string.Format("DELETE Profiles WHERE LastActivityDate < '{0:F}'", userInactiveSinceDate) :
                string.Format("DELETE Profiles WHERE Authenticated = {0} AND LastActivityDate < '{1:F}'", (int)authenticationOption, userInactiveSinceDate);

            UnitOfWork.ExecuteSql(deleteSql);
            return 1;
        }

        /// <summary>
        /// Deletes the profiles.
        /// </summary>
        /// <param name="userNames">The user names.</param>
        /// <returns></returns>
        public override int DeleteProfiles(string[] userNames)
        {
            UnitOfWork.DeleteProfiles(userNames);
            return 1;
        }

        /// <summary>
        /// When overridden in a derived class, deletes profile properties and information for the supplied list of profiles.
        /// </summary>
        /// <param name="profiles">A <see cref="T:System.Web.Profile.ProfileInfoCollection"/>  of information about profiles that are to be deleted.</param>
        /// <returns>
        /// The number of profiles deleted from the data source.
        /// </returns>
        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {
            var stringBuilder = new StringBuilder();
            foreach (ProfileInfo profile in profiles)
            {

               stringBuilder.AppendFormat("EXECUTE [dbo].[DeleteProfiles] @authenticated={0},@userName='{1}';", Convert.ToInt32(profile.IsAnonymous), profile.UserName);
            }

            if (stringBuilder.Length > 0)
            {
                UnitOfWork.ExecuteSql(stringBuilder.ToString());
            }
            return 1;
        }

        /// <summary>
        /// Finds the name of the inactive profiles by user.
        /// </summary>
        /// <param name="authenticationOption">The authentication option.</param>
        /// <param name="userNameToMatch">The user name to match.</param>
        /// <param name="userInactiveSinceDate">The user inactive since date.</param>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="pageSize">Size of the page.</param>
        /// <param name="totalRecords">The total records.</param>
        /// <returns></returns>
        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string userNameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {

            IList<Profile> profiles;

            if (authenticationOption.Equals(ProfileAuthenticationOption.All))
            {
                profiles = UnitOfWork.Search<Profile>(p => p.Username.Contains(userNameToMatch) && p.LastActivityDate < userInactiveSinceDate);
            }
            else
            {
                var temp = Convert.ToBoolean(authenticationOption);
                profiles = UnitOfWork.Search<Profile>(p => p.Authenticated == temp && p.Username.Contains(userNameToMatch) && p.LastActivityDate < userInactiveSinceDate);
            }

            var profileInfoCollection = new ProfileInfoCollection();
            foreach (var profileInfo in profiles.Select(profile => new ProfileInfo(profile.Username, profile.Authenticated, profile.LastActivityDate, profile.LastActivityDate, profile.ProfileData.Count)))
            {
                profileInfoCollection.Add(profileInfo);
            }

            totalRecords = profileInfoCollection.Count;
            return profileInfoCollection;
        }

        /// <summary>
        /// Finds the name of the profiles by user.
        /// </summary>
        /// <param name="authenticationOption">The authentication option.</param>
        /// <param name="userNameToMatch">The user name to match.</param>
        /// <param name="pageIndex">Index of the page.</param>
        /// <param name="pageSize">Size of the page.</param>
        /// <param name="totalRecords">The total records.</param>
        /// <returns></returns>
        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string userNameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {

            IList<Profile> profiles;

            if (authenticationOption.Equals(ProfileAuthenticationOption.All))
            {
                profiles = UnitOfWork.Search<Profile>(p => p.Username.Contains(userNameToMatch));
            }
            else
            {
                var temp = Convert.ToBoolean(authenticationOption);
                profiles = UnitOfWork.Search<Profile>(p => p.Authenticated == temp && p.Username.Contains(userNameToMatch));
            }

            var profileInfoCollection = new ProfileInfoCollection();
            foreach (var profileInfo in profiles.Select(profile => new ProfileInfo(profile.Username, profile.Authenticated, profile.LastActivityDate, profile.LastActivityDate, profile.ProfileData.Count)))
            {
                profileInfoCollection.Add(profileInfo);
            }

            totalRecords = profileInfoCollection.Count;
            return profileInfoCollection;
        }

        /// <summary>
        /// When overridden in a derived class, retrieves user-profile data from the data source for profiles in which the last activity date occurred on or before the specified date.
        /// </summary>
        /// <param name="authenticationOption">One of the <see cref="T:System.Web.Profile.ProfileAuthenticationOption"/> values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="userInactiveSinceDate">A <see cref="T:System.DateTime"/> that identifies which user profiles are considered inactive. If the <see cref="P:System.Web.Profile.ProfileInfo.LastActivityDate"/>  of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
        /// <param name="pageIndex">The index of the page of results to return.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Profile.ProfileInfoCollection"/> containing user-profile information about the inactive profiles.
        /// </returns>
        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            IList<Profile> profiles;

            if (authenticationOption.Equals(ProfileAuthenticationOption.All))
            {
                profiles = UnitOfWork.Search<Profile>(p => p.LastActivityDate < userInactiveSinceDate);
            }
            else
            {
                var temp = Convert.ToBoolean(authenticationOption);
                profiles = UnitOfWork.Search<Profile>(p => p.Authenticated == temp && p.LastActivityDate < userInactiveSinceDate);
            }

            var profileInfoCollection = new ProfileInfoCollection();
            foreach (var profileInfo in profiles.Select(profile => new ProfileInfo(profile.Username, profile.Authenticated, profile.LastActivityDate, profile.LastActivityDate, profile.ProfileData.Count)))
            {
                profileInfoCollection.Add(profileInfo);
            }

            totalRecords = profileInfoCollection.Count;
            return profileInfoCollection;

        }

        /// <summary>
        /// When overridden in a derived class, retrieves user profile data for all profiles in the data source.
        /// </summary>
        /// <param name="authenticationOption">One of the <see cref="T:System.Web.Profile.ProfileAuthenticationOption"/> values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="pageIndex">The index of the page of results to return.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Profile.ProfileInfoCollection"/> containing user-profile information for all profiles in the data source.
        /// </returns>
        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            IList<Profile> profiles;

            if (authenticationOption.Equals(ProfileAuthenticationOption.All))
            {
                profiles = UnitOfWork.Search<Profile>();
            }
            else
            {
                var temp = Convert.ToBoolean(authenticationOption);
                profiles = UnitOfWork.Search<Profile>(p => p.Authenticated == temp);
            }

            var profileInfoCollection = new ProfileInfoCollection();
            foreach (var profileInfo in profiles.Select(profile => new ProfileInfo(profile.Username, profile.Authenticated, profile.LastActivityDate, profile.LastActivityDate, profile.ProfileData.Count)))
            {
                profileInfoCollection.Add(profileInfo);
            }

            totalRecords = profileInfoCollection.Count;
            return profileInfoCollection;

        }

        /// <summary>
        /// When overridden in a derived class, returns the number of profiles in which the last activity date occurred on or before the specified date.
        /// </summary>
        /// <param name="authenticationOption">One of the <see cref="T:System.Web.Profile.ProfileAuthenticationOption"/> values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="userInactiveSinceDate">A <see cref="T:System.DateTime"/> that identifies which user profiles are considered inactive. If the <see cref="P:System.Web.Profile.ProfileInfo.LastActivityDate"/>  of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
        /// <returns>
        /// The number of profiles in which the last activity date occurred on or before the specified date.
        /// </returns>
        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            if (authenticationOption.Equals(ProfileAuthenticationOption.All))
            {
                return UnitOfWork.Search<Profile>(p => p.LastActivityDate < userInactiveSinceDate).Count();
            }
            var temp = Convert.ToBoolean(authenticationOption);
            return UnitOfWork.Search<Profile>(p => p.LastActivityDate < userInactiveSinceDate &&
                p.Authenticated == temp
                ).Count();
        }

        /// <summary>
        /// Initializes the provider.
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
        /// <exception cref="T:System.ArgumentNullException">The name of the provider is null.</exception>
        /// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
        /// <exception cref="T:System.InvalidOperationException">An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"/> on a provider after the provider has already been initialized.</exception>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null) { throw new ArgumentNullException("config"); }

            if (string.IsNullOrWhiteSpace(name))
            {
                name = "PortalProfileProvider";
            }
            base.Initialize(name, config);
            ApplicationName = ConfigHelper.SafeConfigRead("applicationName", config);
            ConnectionStringName = ConfigHelper.SafeConfigRead("connectionStringName", config);
            CreateDatabaseObjects = ConfigHelper.SafeConfigBooleanRead("createDatabaseObjects", config,false);
        }
    }
}